package com.ctw.tinyservices.id.core.snowflake.generator;

import com.ctw.tinyservices.id.common.enums.state.IdLifecycleState;
import com.ctw.tinyservices.id.common.exception.IdStateException;
import com.ctw.tinyservices.id.common.utils.LogUtils;
import com.ctw.tinyservices.id.core.AbstractIdGenerator;
import com.ctw.tinyservices.id.core.snowflake.exception.ClockBackException;
import com.ctw.tinyservices.id.core.snowflake.exception.OverMaxTimestampException;
import com.ctw.tinyservices.id.core.snowflake.exception.ZookeeperConnectFailException;
import com.ctw.tinyservices.id.core.snowflake.holder.AbstractIdSnowflakeHolder;
import com.google.common.base.Preconditions;

import java.util.Random;

/**
 * @author TongWei.Chen 2022/3/28 19:09
 *
 * 雪花算法id生成器
 **/
public class IdSnowflakeGenerator extends AbstractIdGenerator {

    /**
     * 起始的时间戳
     */
    private final long epoch;
    /**
     * 时间戳占用的位数
     */
    private final long timestampBits = 41L;
    /**
     * 序列号占用的位数
     */
    private final long sequenceBits = 12L;
    /**
     * 机器标识占用的位置
     */
    private final long workerIdBits = 10L;
    /**
     * 自增ID的最大值
     */
    private final long maxSequence = ~(-1L << sequenceBits);
    /**
     * 最大能够分配的workerId = 1023
     */
    private final long maxWorkerId = ~(-1L << workerIdBits);

    private final long workerIdShift = sequenceBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits;
    /**
     * 机器标识
     */
    private long workerId;
    /**
     * 序列号
     */
    private long sequence = 0L;
    /**
     * 上一次时间戳
     */
    private long lastTimestamp = -1L;
    /**
     * 生成id的最大时间戳，当前时间戳超过 maxTimestamp，生成 id 将为负数
     * 计算公式：~(-1L << timestampBits) + epoch
     */
    private final long maxTimestamp;

    private static final Random RANDOM = new Random();

    private AbstractIdSnowflakeHolder idSnowflakeHolder;

    public IdSnowflakeGenerator(int workerId) {
        // BeiJing: 2022-04-01 00:00:00
        this(workerId, 1648742400000L);
    }

    public IdSnowflakeGenerator(int workerId, long epoch) {
        this.workerId = workerId;
        this.epoch = epoch;
        this.maxTimestamp = ~(-1L << timestampBits) + epoch;
        Preconditions.checkArgument(now() > epoch, "Snowflake not support epoch gt currentTime");
    }

    public IdSnowflakeGenerator(AbstractIdSnowflakeHolder idSnowflakeHolder) {
        // BeiJing: 2022-04-01 00:00:00
        this(idSnowflakeHolder, 1648742400000L);
    }

    public IdSnowflakeGenerator(AbstractIdSnowflakeHolder idSnowflakeHolder, long epoch) {
        this.epoch = epoch;
        this.maxTimestamp = ~(-1L << timestampBits) + epoch;
        Preconditions.checkArgument(now() > epoch, "Snowflake not support epoch gt currentTime");
        this.idSnowflakeHolder = idSnowflakeHolder;
    }

    @Override
    public Long getId(String bizType) {
        if (state != IdLifecycleState.START) {
            LogUtils.error(IdSnowflakeGenerator.class, "Init snowflake error， please see doStart method log, bizType is [{}]", bizType);
            throw new IdStateException("Init snowflake error， please see doStart method log");
        }
        if (idSnowflakeHolder != null && ! idSnowflakeHolder.isAlive()) {
            LogUtils.error(IdSnowflakeGenerator.class, "lost connect to zookeeper over maxDisConnectTime, cannot generate id");
            throw new ZookeeperConnectFailException("lost connect to zookeeper over maxDisConnectTime, cannot generate id");
        }
        long currentTimestamp = now();
        if (currentTimestamp > maxTimestamp) {
            throw new OverMaxTimestampException("current timestamp is over maxTimeStamp, the generate id will be negative");
        }
        // 时钟回拨
        if (currentTimestamp < lastTimestamp) {
            long offset = lastTimestamp - currentTimestamp;
            if (offset <= 5) {
                try {
                    wait(offset << 1);
                    currentTimestamp = now();
                    if (currentTimestamp < lastTimestamp) {
                        LogUtils.error(IdSnowflakeGenerator.class, "clock back!!!");
                        throw new ClockBackException("clock back");
                    }
                } catch (InterruptedException e) {
                    LogUtils.error(IdSnowflakeGenerator.class, "wait interrupted");
                    throw new ClockBackException("clock back");
                }
            } else {
                throw new ClockBackException("clock back");
            }
        }
        if (lastTimestamp == currentTimestamp) {
            // 相同毫秒内，序列号自增
            sequence = (sequence + 1) & maxSequence;
            if (sequence == 0) {
                // seq 为0的时候表示是同一毫秒的序列数已经达到最大，也就是下一毫秒时间对开始，因此对seq做随机
                sequence = RANDOM.nextInt(100);
                currentTimestamp = tilNextMillis();
            }
        } else {
            // 如果是新的ms开始，也对seq做随机
            sequence = RANDOM.nextInt(100);
        }
        lastTimestamp = currentTimestamp;
        return ((currentTimestamp - epoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;
    }

    protected long tilNextMillis() {
        long timestamp = now();
        while (timestamp <= lastTimestamp) {
            timestamp = now();
        }
        return timestamp;
    }

    private long now() {
        return System.currentTimeMillis();
    }

    @Override
    protected void doInit() {
        idSnowflakeHolder.init();
        this.workerId = idSnowflakeHolder.getWorkerId();
        Preconditions.checkArgument(workerId >= 0 && workerId <= maxWorkerId, "workerID must gte 0 and lte 1023");
    }

    @Override
    protected void doStart() {
        idSnowflakeHolder.start();
    }

    @Override
    protected void doStop() {
        idSnowflakeHolder.stop();
    }

    @Override
    public String name() {
        return "snowflake";
    }
}
